﻿/*
 * DATAFLOWCORE
 * 
Copyright 2012 - Mindstorm Multitouch Limited

Author - Bertrand Nouvel

DataFlowCore is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

DataFlowCore is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Lesser Public License for more details.
*/



using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MongoDB.Bson;

namespace DFlow
{
    /// <summary>
    /// Dataflow are the base concept of DataFlow Core
    /// 
    /// A data flow is a set nodes that should perform some computations 
    /// according to their input and outputs.
    /// 
    /// The nodes are processed in a specific order [ currently they are processed in their creation order ].
    /// 
    /// During the update of each nodes different event handler are triggered on node monitors
    /// 
    /// Wires are special kind of node monitor that convey the information from an output pin
    /// to another input or to the parameters of a node.
    /// 
    /// Inputs of nodes are meant to be elements that vary with input of the dataflow.
    /// Parameters on the other hand are meant to be generally more persistent.
    /// 
    /// In some case it may be require to wire parameters of nodes and thus they may also
    /// be considered as input & output
    /// 
    /// When parameters are modified the function UpdateParameters of the node is always called.
    /// 
    /// Dataflow core uses the declarations in order to transform automatically the data as required.
    /// 
    /// It is convenient to qualify a set of pins an parameters as possible dataflow input, outputs & parameters.
    /// The input & output pin of the dataflow, are named reference to external parameters.
    /// The fact that they are name allow us to use them with the create wire primitive that 
    /// will also create the converter.
    /// 

    /// </summary>
    public class Dataflow : IDisposable
    {
        /// <summary>
        /// Specifies if the dataflow is to be runned automatically by the engine at each engine cycle
        /// or if the dataflow is trigerred by some external engine.
        /// </summary>
        public enum RunMode
        {
            Automatic,
            Manual
        };

        /// <summary>
        /// Each data flow has a name.
        /// </summary>
        public string name;

        /// <summary>
        /// Dataflow may be included in other dataflows
        /// </summary>
        public Node parent_Node = null;
        // THIS IS TEMPORARY... (TO BE IMPROVED PER BRANCH METADATA ?...)
        public Dictionary<string, Func<object, object>> _Metadata = new Dictionary<string, Func<object, object>>();

        public void SetMetadata(string s, Func<object, object> f)
        {
            _Metadata[s] = f;
        }

        public Func<object, object> GetMetadata(string s)
        {
            if (_Metadata.ContainsKey(s))
            {
                return _Metadata[s];
            }

            if (parent_Dataflow != null)
            {
                return parent_Dataflow.GetMetadata(s);
            }

            throw new KeyNotFoundException();
        }

        public bool HasMetadata(string s)
        {
            if (_Metadata.ContainsKey(s))
            {
                return true;
            }

            if (parent_Dataflow != null)
            {
                return parent_Dataflow.HasMetadata(s);
            }

            return false;
        }

        private System.Collections.Generic.List<Node> nodes;
        public delegate bool OnAddNodeD(Node n);
        public delegate bool OnDeleteNodeD(Node n);
        public delegate bool OnBeforeProcessD(Dataflow p);
        public delegate bool OnAfterProcessD(Dataflow p);

        public event OnBeforeProcessD OnBeforeProcess;
        public event OnAfterProcessD OnAfterProcess;
        public event OnAddNodeD OnAddNode;
        public event OnDeleteNodeD OnDeleteNode;

        public RunMode runmode = RunMode.Automatic;            // should the Dataflow be run by process or trigerred by another soft
        public Dataflow parent_Dataflow;                     // may be queried for process metadata on another address
        public object data_address = null;                     // address of the data currently processed by this Dataflow

        public enum DataflowState
        {
            Ready,
            RequiresTraining,
            Error,
            Unknown
        }

        public DataflowState _state = DataflowState.Unknown;



        public BsonDocument Save()
        {
            BsonDocument b = new BsonDocument();
            BsonDocument cnodes = new BsonDocument();
            BsonDocument cwires = new BsonDocument();
            //BsonDocument variables = new BsonDocument();

            foreach (DFlow.Node n in nodes)
            {
                cnodes.Add(n.name, new BsonDocument(n.GetNodetype(), DFlowCore.BsonUtils.BsonEncode(n.GetParameters(), n.GetParameters().GetType())));
                foreach (string wsrcpin in n.ListOutputNames())
                {
                    BsonArray targets = new BsonArray();
                    foreach (DFlow.Wire w in n.GetOutboundWires().Where(tw => (wsrcpin == tw.srcpin)))
                    {
                        targets.Add(new BsonString(w.dstpin + "@" + w.dstnode.name));
                    }
                    cwires.Add(wsrcpin + "@" + n.name, targets);
                }
            }

            b.Add("nodes", cnodes);
            b.Add("wires", cwires);
            //System.Diagnostics.Debug.Print(b.ToJson());
            return b;
        }

        public void Load(BsonDocument bd, System.Collections.Generic.Dictionary<string, System.Type> nodetypes)
        {
            Reset();
            BsonDocument tnodes = bd["nodes"].AsBsonDocument;
            foreach (BsonElement el in tnodes.Elements)
            {
                BsonDocument xbd = el.Value.AsBsonDocument;
                IEnumerator<BsonElement> elt = xbd.Elements.GetEnumerator();
                elt.MoveNext();
                DFlow.Node xn = nodetypes[elt.Current.Name].InvokeMember(null, System.Reflection.BindingFlags.CreateInstance, null, null, null) as DFlow.Node;
                xn.UpdateParameters(elt.Current.Value.AsBsonDocument);
                xn.name = el.Name;
                this.AddNode(xn);
            }
            BsonDocument twires = bd["wires"].AsBsonDocument;
            foreach (BsonElement el in twires.Elements)
            {
                int pp = el.Name.IndexOf('@');
                string srcpin = el.Name.Substring(0, pp);
                string srcnode = el.Name.Substring(pp + 1);
                Node s = Find(srcnode);
                if (el.Value.IsString)
                {
                    pp = el.Value.AsString.IndexOf('@');
                    string dstpin = el.Value.AsString.Substring(0, pp);
                    string dstnode = el.Value.AsString.Substring(pp + 1);
                    Node d = Find(dstnode);
                    s.AddWire(new Wire(s, srcpin, d, dstpin));
                }
                else
                {
                    foreach (BsonValue tbv in el.Value.AsBsonArray)
                    {
                        pp = tbv.AsString.IndexOf('@');
                        string dstpin = tbv.AsString.Substring(0, pp);
                        string dstnode = tbv.AsString.Substring(pp + 1);
                        Node d = Find(dstnode);
                        s.AddWire(new Wire(s, srcpin, d, dstpin));
                    }
                }
            }

        }



        public void Reset()
        {
            List<string> r = new List<string>(nodes.Select(x => x.name));
            foreach (string n in r)
            {
                this.DeleteNode(n);
            }
        }

        public void AddNode(Node n)
        {
            if (n.host_Dataflow != null)
            {
                throw new System.Exception("This node is already attached to another dataflow.");
            }
            if ((n.name == null) || (n.name.Length == 0))
            {
                n.name = System.String.Format("unnamed-{0}-{1}", DFlowCore.Time.time, DFlowCore.Random.Range(0, 65535));
            }
            if (Find(n.name) != null)
            {
                throw new System.Exception("A node with a similar name already exists in the dataflow.");
            }

            nodes.Add(n);
            n.host_Dataflow = this;
            n.CheckInputs();
            n.CheckOutputs();
            if (OnAddNode != null)
            {
                OnAddNode(n);
            }
        }

        public Node Find(string n)
        {
            foreach (Node cn in nodes)
            {
                if (cn.name == n)
                {
                    return cn;
                }
            }
            return null;
        }

        public void DeleteNode(string nn)
        {

            foreach (Node cn in nodes.FindAll(n => n.name == nn))
            {
                if (OnDeleteNode != null)
                {
                    OnDeleteNode(cn);
                }
                cn.host_Dataflow = null;
                cn.Dispose();

            }

            nodes.RemoveAll(n => n.name == nn);

        }
        public void DeleteNode(Node cn)
        {

            if (OnDeleteNode != null)
            {
                OnDeleteNode(cn);
            }

            cn.Dispose();
            cn.host_Dataflow = null;
            nodes.Remove(cn);

        }

        public void DeleteNodeAndSubsequentNodes(Node n)
        {
            if (n.host_Dataflow != this)
            {
                throw new System.Exception();
            }

            List<Node> subsequentnodes = new List<Node>();
            foreach (Wire w in n.GetOutboundWires())
            {
                if (!subsequentnodes.Contains(w.dstnode))
                {
                    subsequentnodes.Add(w.dstnode);
                }
            }
            DeleteNode(n);

            foreach (Node cn in subsequentnodes)
            {
                DeleteNodeAndSubsequentNodes(cn);
            }
        }


        public void Dispose()
        {
            Reset();
        }

        public IEnumerable<DFlow.Node> GetNodes()
        {
            foreach (DFlow.Node n in nodes)
            {
                yield return n;
            }

        }


        System.DateTime lastProcess;



        public void Process()
        {
            if (OnBeforeProcess != null)
            {
                OnBeforeProcess(this);
            }

            /// FIXME: MOVE ME IN A MODULE
            System.DateTime tstart = System.DateTime.Now;
            
            foreach (Node n in nodes)
            {
                if (n.IsReady())
                {
                    foreach (NodeMonitor nm in n.GetMonitors())
                    {
                        if (nm.enabled)
                        {
                            nm.OnPreProcess();
                        }
                    }
                    n.Process();
                    foreach (NodeMonitor nm in n.GetMonitors())
                    {
                        if (nm.enabled)
                        {
                            nm.OnPostProcess();
                        }
                    }
                }
            }


            if (OnAfterProcess != null)
            {
                OnAfterProcess(this);
            }



            ///
            /// FIXME : MOVE ME IN A MODULE !
            /// 
            System.DateTime tend = System.DateTime.Now;
            System.TimeSpan ts = tend - tstart;
            double d = ts.TotalSeconds;
            if ((tend.Second / 5) != (lastProcess.Second / 5))
            {
                DFlowCore.Log.Info(
                    "last duration :" 
                    + d.ToString() 
                    + ((d > 0) ? (" i.e." + (1d / d).ToString() + "fps") : ""),1
                    );
            }
            lastProcess = tend;
        }




        /// <summary>
        /// Syntactic sugar 
        ///     This function tries to automatically bind src to dst.
        ///     Current implementation is greedy
        /// </summary>
        /// <param name="src"></param>
        /// <param name="dst"></param>
        /// <returns></returns>
        public int AutoWire(DFlow.Node src, DFlow.Node dst)
        {
            int c = 0;
            List<System.Reflection.FieldInfo> srcopins=new List<System.Reflection.FieldInfo>(src.ListOutputPins());
            bool [] usedpins = new bool[srcopins.Count()];
            for (int i = 0; i < usedpins.Length; i++) usedpins[i] = false;
            foreach(System.Reflection.FieldInfo f in dst.ListInputPins()) {
                bool found=false;
                for (int i=0;i<srcopins.Count;i++) {
                    if ((!usedpins[i])&&(srcopins[i].FieldType==f.FieldType)) {
                        src.AddWire(new Wire(src,srcopins[i].Name,dst,f.Name));                      
                        usedpins[i]=true;
                        found=true;
                        break;
                    }
                }
                if (!found) {
                for (int i=0;i<srcopins.Count;i++) {
                    if ((!usedpins[i])&&(Converters.GetConverter(srcopins[i].FieldType,f.FieldType, true)!=null)) {
                        src.AddWire(new Wire(src,srcopins[i].Name,dst,f.Name));
                        usedpins[i]=true;
                        found=true;
                        break;
                    }
                }
                }
                if (!found) {
                for (int i=0;i<srcopins.Count;i++) {
                    if ((!usedpins[i]) && (Converters.GetConverter(srcopins[i].FieldType, f.FieldType, false) != null))
                    {
                        src.AddWire(new Wire(src,srcopins[i].Name,dst,f.Name));
                        usedpins[i]=true;
                        found=true;
                        break;

                    }
                }
                }
                if (found) c++;
            }
            return c;
        }


        public Dataflow()
        {
            name = "DFlow Dataflow";
            nodes = new List<Node>();
            lastProcess = System.DateTime.Now;
        }

        protected struct DataFlowPin {
            public Type type;
            public Node node;
            public string pinname;
        }

        protected List< DataFlowPin > df_inputs;
        protected List< DataFlowPin > df_outputs;
        protected List< DataFlowPin > df_parameters;

        public void RegisterDataflowInputPin(Node n, string pinname)
        {
            if (pinname[0] == '*')
            {
                System.Type t = n.GetParameters().GetType().GetField(pinname.Substring(1)).FieldType;
                df_inputs.Add(new DataFlowPin()
                {
                    type = t,
                    node = n,
                    pinname = pinname
                });
            }
            else
            {
                System.Type t = n.GetType().GetField(pinname).FieldType;
                df_inputs.Add(new DataFlowPin()
                {
                    type = t,
                    node = n,
                    pinname = pinname
                });
            }
        }

        public void RegisterDataflowOutputPin(Node n, string pinname)
        {
            if (pinname[0] == '*')
            {
                System.Type t = n.GetParameters().GetType().GetField(pinname.Substring(1)).FieldType;
                df_outputs.Add(new DataFlowPin()
                {
                    type = t,
                    node = n,
                    pinname = pinname
                });
            }
            else
            {
                System.Type t = n.GetType().GetField(pinname).FieldType;
                df_outputs.Add(new DataFlowPin()
                {
                    type = t,
                    node = n,
                    pinname = pinname
                });
            }
        }


        public void RegisterDataflowParameterPin(Node n, string pinname)
        {
            if (pinname[0] == '*')
            {
                System.Type t = n.GetParameters().GetType().GetField(pinname.Substring(1)).FieldType;
                df_outputs.Add(new DataFlowPin()
                {
                    type = t,
                    node = n,
                    pinname = pinname
                });
            }
            else
            {
                DFlowCore.Log.Error("Dataflow parameters may only be node parameters");
                System.Diagnostics.Debug.Assert(false);
            }
        }

        public void AddWire(Wire w)
        {            
            //System.Diagnostics.Debug.Assert(nodes.Contains, w.srcnode);
            w.srcnode.AddWire(w);
        }

        /*
        public  IEnumerable<Node> ListDataflowPotentialInputPin()
        {
            foreach(Node n in nodes) {

                // look for nodes without
                if (!n.GetInboundWires(false).GetEnumerator().MoveNext())
                {

                }

            }
        }*/

    }


}
